import pathlib
from ..BaseCore.Selenium电商抽象类 import ShopOnline_CreateProduct
from ..BaseCore.Selenium电商抽象类 import WebDriver,WebElement,Service,Options,By
from ..BaseCore.Selenium电商抽象类 import abstractmethod
import json,typing


def group_by_key(
    items: typing.List[typing.Dict[str, typing.Any]],
    key_path: str, 
    sep: str = "."  # 路径分隔符，默认用.分隔嵌套层级
) -> typing.Dict[typing.Any, typing.List[typing.Dict[str, typing.Any]]]:
    """
    按指定键（支持嵌套路径）对字典列表进行分组，保持原始数据结构不变
    
    Args:
        items: 原始字典列表（支持嵌套字典结构）
        key_path: 分组依据的键路径（如单层键"price"，或嵌套键"parent.child"）
        sep: 路径分隔符，默认用.分隔（可自定义为其他符号如/）
    
    Returns:
        分组后的字典，键为路径对应的取值，值为包含原始字典的列表（数据结构不变）
    
    Raises:
        ValueError: 当键路径不存在或数据结构不匹配时抛出
    """
    groups = {}
    # 拆分路径为层级列表（如"a.b.c"拆分为["a", "b", "c"]）
    keys = key_path.split(sep)
    
    for item in items:
        current = item  # 用于逐层遍历嵌套结构
        try:
            # 按路径逐层获取值（处理嵌套）
            for key in keys:
                current = current[key]
            # 此时current就是路径对应的最终值（分组依据）
            group_key = current
        except (KeyError, TypeError) as e:
            # KeyError：路径中的某个键不存在
            # TypeError：中间节点不是字典（无法继续取值）
            raise ValueError(f"键路径 '{key_path}' 在元素 {item} 中无效：{str(e)}")
        
        # 按最终取值分组，保留原始item（数据结构不变）
        if group_key not in groups:
            groups[group_key] = []
        groups[group_key].append(item)
    
    return groups


class DouDian(ShopOnline_CreateProduct):
    def __init__(self) -> None:
        super().__init__()

    
    def LoadConfigFile(self, config_file = "config.json") -> typing.Self:
        """加载配置文件"""
        from ..BaseCore.ConfigManager import ConfigManager
        self.config = ConfigManager().load_config(config_file)
        # self.config : dict = json.load(fp=open(file=config_file, mode="r", encoding="utf-8"))
        self.fristcatgeory = self.config["dd_fristcatgeory"]
        self.secondcatgeory = self.config["dd_secondcatgeory"]
        self.thirdcatgeory = self.config["dd_thirdcatgeory"]
        self.fourthcatgeory = self.config['dd_fourthcatgeory']
        self.producttitle = self.config['dd_producttitle']
        
        self.main_images = self.config.get("main_images",[])
        self.detail_images = self.config.get("detail_images",[])
        self.sku_images = self.config.get("sku_images",[])        
        self.main_video = self.config.get("main_video","")
        
        self.attributes = self.config.get("dd_attributes",[])
        self.materials = self.config.get("dd_materials",[])
        self.number = self.config.get("dd_number",10000)
        
        self.sizes = self.config.get("dd_sizes",{})
        self.size_chart = self.config.get("dd_size_chart",[])
        return self


    def OpenCreateProductPage(self) -> typing.Self:
        """打开创建产品页面"""
        
        self.log.info(f"正在打开抖店创建产品页面!")
        
        _Create_Product_Page = "https://fxg.jinritemai.com/ffa/g/create"
        self.driver.switch_to.new_window("tab")
        self.driver.get(url=_Create_Product_Page)
        return self


    def SelectFirstCategory(self) -> typing.Self:
        """选择一级分类"""
        
        self.log.info(f"抖店目前不支持手动选择一级分类，目前为自动选择分类!")
        
        return self


    def SelectSecondCategory(self) -> typing.Self:
        """选择二级分类"""
        
        self.log.info(f"抖店目前不支持手动选择二级分类，目前为自动选择分类!")
        
        return self


    def SelectThirdCategory(self) -> typing.Self:
        """选择三级分类"""
        
        self.log.info(f"抖店目前不支持手动选择三级分类，目前为自动选择分类!")
        
        return self


    def SelectfourthCategory(self) -> typing.Self:
        """选择四级分类"""
        
        self.log.info(f"抖店目前不支持手动选择四级分类，目前为自动选择分类!")
        
        return self


    def GotoParamSettingPage(self) -> typing.Self :
        """跳转到参数设置页面"""
        
        self.log.info(f"正在跳转到参数设置页面!")
        
        self.finder.chain_wait_element(xpath="//button[not(@disabled)]/span[text()='下一步']",timeout=10)
        self.finder.chain_find_element_safe(xpath="//button[not(@disabled)]/span[text()='下一步']",timeout=10)
        self.mouse.chain_safe_click()
        return self


    def FillProductTitle(self) -> typing.Self :
        """填写产品标题"""
        
        self.log.info(f"正在填写产品标题 [ {self.producttitle} ]!")
        
        self.finder.chain_find_element_safe(xpath="//*[text()='商品标题']/ancestor::*[position()<6]//input")
        if self.current_element : self.keyboard.check_input(InputValue=self.producttitle)
        return self


    def FillProductParams(self) -> typing.Self :
        """填写产品参数"""
        
        self.log.info(f"正在填写产品参数!")
        
        # 遍历所有的属性
        for item_attribute in self.attributes:
            attributeName = item_attribute['attribute']
            attributeValue = item_attribute['value']
            
            self.log.info(f"正在填写产品参数 [ {attributeName} ] [ {attributeValue} ]!")
            
            self.finder.chain_find_element_safe(xpath=f"//*[@attr-field-id='类目属性']//*[text()='{attributeName}']/following::input[1]")
            self.mouse.chain_condition_click(condition_xpath=f"//div[@class='rc-virtual-list']/div[@class='rc-virtual-list-holder']//div[text()='{attributeValue}']")
            
            # 根据内容进行选择
            if isinstance(attributeValue,str):
                self.keyboard.chain_safe_input(InputValue=attributeValue).parent.base.wait(1)
                self.finder.chain_find_element_safe(xpath=f"//div[@class='rc-virtual-list']/div[@class='rc-virtual-list-holder']//div[text()='{attributeValue}']")
                self.mouse.chain_safe_click()

            elif isinstance(attributeValue,list):
                for attributeValueItem in attributeValue:
                    self.finder.chain_find_element_safe(xpath=f"//div[contains(@class,'select-tree-treenode')]//span[text()='{attributeValueItem}']").parent.mouse.chain_safe_click().parent.base.wait(1)
            
        return self


    def FillProductMaterial(self) -> typing.Self :
        """填写产品材质"""
        
        self.log.info(f"正在填写产品材质!")
        
        # 遍历所有的面料
        for index, item_material in enumerate(self.materials):
            materialName = item_material['material']
            materialValue = item_material['value']

            self.log.info(f"正在填写产品材质 [ {materialName} ] [ {materialValue} ]!")

            # 输入面料
            self.finder.chain_find_element_safe(xpath=f"//*[@attr-field-id='类目属性']//*[text()='面料材质']/following::input[not(@type='file')][{index * 2 + 1}]").parent.keyboard.chain_safe_input(InputValue=materialName).parent.base.wait(2)
            self.finder.chain_find_element_safe(xpath=f"//div[@class='rc-virtual-list']//*[text()='{materialName}']").parent.mouse.chain_safe_click()

            # 输入值
            self.finder.chain_find_element_safe(xpath=f"//*[@attr-field-id='类目属性']//*[text()='面料材质']/following::input[not(@type='file')][{index * 2 + 2}]").parent.keyboard.chain_safe_input(InputValue=materialValue).parent.base.wait(2)
        
            # 判断是否是最后一个元素
            if index != len(self.materials) - 1:
                self.finder.chain_find_element_safe(xpath="//*[@attr-field-id='类目属性']//button/span[text()='添加材质']")
                self.mouse.script_click()

        return self


    def FillProductMainImage(self) -> typing.Self:
        """填写产品主图"""

        self.log.info("正在填写产品主图")
        self.log.info(f"正在上传图片 : {'\n'.join(self.main_images)}")
        self.finder.chain_find_element_safe(xpath="//*[text()='主图上传']/ancestor::*[position()<6]//input[@type='file']")
        self.keyboard.chain_send_keys(InputValue="\n".join(self.main_images))
        return self
    
    def FillProductDetailImage(self) -> typing.Self:
        """填写产品详情图"""
        
        self.log.info("正在填写产品详情图!")
        self.log.info(f"正在上传图片 : {'\n'.join(self.detail_images)}")

        # 1.清空所有的详情图
        # 获取所有删除图标并逐一点击删除    
        for e in self.driver.find_elements(by=By.XPATH,value="//*[@attr-field-id='商品详情']//i[contains(@class,'iconDelete')]"):
            self.driver.execute_script("arguments[0].click();",e)
            self.base.wait(0.5)
        
        # 2.上传所有详情图
        self.finder.chain_find_element_safe(xpath="//*[@attr-field-id='商品详情']//*[text()='详情编辑']/following::input[1][@type='file']")
        self.keyboard.chain_send_keys(InputValue="\n".join(self.detail_images))
        return self


    def FillProductSku(self) -> typing.Self:
        """填写产品SKU"""
        
        self.log.info("正在填写产品SKU!")
        
        for index,skuitem in enumerate(self.sku_images):
            skuName = pathlib.Path(skuitem["path"]).stem
            imagePath = skuitem["path"]
            
            self.log.info(f"正在填写第 {index + 1} / {self.sku_images.__len__()} 个SKU [ {skuName} ]!")
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@class,'skuNameBox')]//*[text()='颜色分类']/following::input[not(@type='file')][not(@placeholder='备注')][{index + 1}]").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath="//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//a").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//input").parent.keyboard.chain_send_keys(InputValue=skuName).parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//span[@alt='确认']").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]/following::button").parent.mouse.chain_safe_click().parent.base.wait(1)

            self.finder.chain_find_element_safe(xpath=f"//*[contains(@class,'skuNameBox')]//*[text()='颜色分类']/following::input[not(@type='file')][not(@placeholder='备注')][{index + 1}]/following::input[@type='file'][1]").parent.keyboard.chain_send_keys(skuitem["path"]).parent.base.wait(1)
        return self

    def FillProductSizes(self) -> typing.Self:
        """填写产品规格"""
        
        self.log.info("正在填写产品规格!")
        
        sizeType = self.sizes['type']
        sizeValues = self.sizes['values']
        
        for index, sizeValue in enumerate(sizeValues):
            
            self.log.info(f"正在填写产品规格：{sizeValue}")
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@class,'skuNameBox')]//*[text()='尺码大小']/following::input[not(@type='file')][not(@placeholder='备注')][{index + 1}]").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath="//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//a").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//input").parent.keyboard.chain_send_keys(InputValue=sizeValue).parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]//span[@alt='确认']").parent.mouse.chain_safe_click().parent.base.wait(1)
            self.finder.chain_find_element_safe(xpath=f"//*[contains(@style, 'left:') and number(substring-before(normalize-space(substring-after(@style, 'left:')), 'px')) > 0]//div[contains(@class,'addSKUName')]/following::button").parent.mouse.chain_safe_click().parent.base.wait(1)
        
        return self
    
    def FillProductVideo(self) -> typing.Self:
        """填写产品视频"""
        if self.main_video == "":
            self.finder.chain_find_element_safe(xpath=f"//div[@attr-field-id='主图视频']//*[text()='一键生成']").parent.mouse.chain_safe_click()
        else:
            self.finder.chain_find_element_safe(xpath=f"//div[@attr-field-id='主图视频']//input[@type='file']").parent.keyboard.chain_only_input(self.main_video)        
        return self


    def FillProductPrice(self) -> typing.Self:
        """填写产品价格"""
        
        self.log.info("正在填写产品价格!")
        
        [e.click() for e in self.driver.find_elements(by=By.XPATH,value="//*[text()='价格与库存']/following::input[position()<2]")]
        price_group = group_by_key(items=self.sku_images,key_path="price")
        
        for price in price_group:
            es = self.driver.find_elements(by=By.XPATH,value="(//div[@id='goodsEditScrollContainer-价格库存']//div[@class='rc-virtual-list-holder-inner'])[1]//div[@aria-selected='true']")
            [self.driver.execute_script("arguments[0].click();",e) for e in es]
            
            self.log.info(f"正在填写产品价格 : {price} 的商品")
                    
            # 选中同一分组的价格
            for skuitem in price_group[price]:
                skuName = pathlib.Path(skuitem['path']).stem
                self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//div[@class='rc-virtual-list-holder-inner']//div[@title='{pathlib.Path(skuName).stem}']").parent.mouse.chain_script_click()

            # 填写价格
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='价格']").parent.keyboard.chain_safe_input(InputValue=price,attribute="value")
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='现货库存']").parent.keyboard.chain_only_clear()
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='商家编码']").parent.keyboard.chain_only_clear()
            
            # 设置
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//*[text()='批量设置']").parent.mouse.chain_safe_click().parent.base.wait(1)

        return self


    def FillProductInventory(self) -> typing.Self:
        """填写产品库存"""
        
        self.log.info(f"正在填写产品库存：{self.number}")
        self.log.info(f"正在设置统一库存 : {self.number}")
        
        [e.click() for e in self.driver.find_elements(by=By.XPATH,value="//*[text()='价格与库存']/following::input[position()<2]")]

        # 关闭所有选中
        es = self.driver.find_elements(by=By.XPATH,value="(//div[@id='goodsEditScrollContainer-价格库存']//div[@class='rc-virtual-list-holder-inner'])[1]//div[@aria-selected='false']")
        [self.driver.execute_script("arguments[0].click();",e) for e in es]
        
        # 填写库存
        self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='价格']").parent.keyboard.chain_only_clear()
        self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='现货库存']").parent.keyboard.chain_safe_input(InputValue=self.number,attribute="value")
        self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='商家编码']").parent.keyboard.chain_only_clear()
        
        # 设置
        self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//*[text()='批量设置']").parent.mouse.chain_safe_click().parent.base.wait(1)
    
        return self


    def FillProductShopLabel(self) -> typing.Self:
        """填写产品商家编号"""
        
        self.log.info(f"正在填写产品商家编号")
        
        [e.click() for e in self.driver.find_elements(by=By.XPATH,value="//*[text()='价格与库存']/following::input[position()<2]")]
        code_group = group_by_key(items=self.sku_images,key_path="code")
        
        for code in code_group:
            es = self.driver.find_elements(by=By.XPATH,value="(//div[@id='goodsEditScrollContainer-价格库存']//div[@class='rc-virtual-list-holder-inner'])[1]//div[@aria-selected='true']")
            [self.driver.execute_script("arguments[0].click();",e) for e in es]
            
            self.log.info(f"正在填写产品商家编号 : {code} 的商品")
            
            for skuitem in code_group[code]:
                skuName = pathlib.Path(skuitem['path']).stem
                self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//div[@class='rc-virtual-list-holder-inner']//div[@title='{pathlib.Path(skuName).stem}']").parent.mouse.chain_script_click()
                
            # 填写价格
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='价格']").parent.keyboard.chain_only_clear()
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='现货库存']").parent.keyboard.chain_only_clear()
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//input[@placeholder='商家编码']").parent.keyboard.chain_safe_input(InputValue=code,attribute="value")
            
            # 设置
            self.finder.chain_find_element_safe(xpath=f"//div[@id='goodsEditScrollContainer-价格库存']//*[text()='批量设置']").parent.mouse.chain_safe_click().parent.base.wait(1)
        return self


    def FillProductLogistics(self) -> typing.Self:
        """填写产品物流信息"""
        
        self.log.info(f"正在填写产品物流信息")
        
        self.finder.chain_find_element_safe(xpath="//*[text()='48小时']")
        self.mouse.chain_safe_click()
        self.base.wait(1)
        
        return self

    def FillSizeCharts(self) -> typing.Self:
        """填写产品尺码表"""
        
        self.log.info(f"正在填写产品尺码表")
        
        # 判断是否需要进行一个开启尺码表,默认开启
        if self.finder.chain_find_element_safe(xpath=f"//div[@attr-field-id='尺码信息']/following::span[text()='开启尺码表']/following::button[@aria-checked='false'][1]").parent.current_element:
            self.mouse.chain_safe_click()
            self.base.wait(5)
        
        # 清空其他的尺码表选择
        for e in self.driver.find_elements(by=By.XPATH,value="(//div[@attr-field-id='尺码信息']/following::div[contains(@class,'checkBoxItemWrapper')]/label)[position() <= 3][not(contains(@class,'checked'))]"):
            e.click()
        
        for index,size_chart in enumerate(self.size_chart):
            # 尺码
            self.finder.chain_find_element_safe(xpath=f"((//div[@attr-field-id='尺码信息']/following::table/tbody/tr[@data-row-key])[{index + 1}]//input)[1]")
            self.keyboard.chain_safe_input(InputValue=size_chart['size'])
            
            # 身高
            self.finder.chain_find_element_safe(xpath=f"((//div[@attr-field-id='尺码信息']/following::table/tbody/tr[@data-row-key])[{index + 1}]//input)[2]")
            self.keyboard.chain_safe_input(InputValue=size_chart['height'])
            
            # 体重
            self.finder.chain_find_element_safe(xpath=f"((//div[@attr-field-id='尺码信息']/following::table/tbody/tr[@data-row-key])[{index + 1}]//input)[3]")
            self.keyboard.chain_safe_input(InputValue=size_chart['weight'])
            
            self.mouse.chain_safe_click()
        
        return self